go 高性能编程/002-更高效的写法
字符串高效拼接
常见字符串拼接方式:5 种
1、使用 +
2、使用 fmt.Sprintf
3、使用 strings.Builder
func builderConcat(n int, str string) string {
var builder strings.Builder
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
4、使用 bytes.Buffer
func bufferConcat(n int, s string) string {
buf := new(bytes.Buffer)
for i := 0; i < n; i++ {
buf.WriteString(s)
}
return buf.String()
}
5、使用 []byte
func byteConcat(n int, str string) string {
buf := make([]byte, 0)
for i := 0; i < n; i++ {
buf = append(buf, str...)
}
return string(buf)
}
strings.Builder
、bytes.Buffer
和 []byte
的性能差距不大,而且消耗的内存也十分接近。
最后建议:一般推荐使用 strings.Builder
来大量拼接字符串。
string.Builder
也提供了预分配内存的方式 Grow
:
func builderConcat(n int, str string) string {
var builder strings.Builder
builder.Grow(n * len(str))
for i := 0; i < n; i++ {
builder.WriteString(str)
}
return builder.String()
}
切片陷阱
在已有切片的基础上进行切片,不会创建新的底层数组。因为原来的底层数组没有发生变化,内存会一直占用,直到没有变量引用该数组。因此很可能出现这么一种情况,原切片由大量的元素构成,但是我们在原切片的基础上切片,虽然只使用了很小一段,但底层数组在内存中仍然占据了大量空间,得不到释放。比较推荐的做法,使用 copy
替代 re-slice
。
如果函数直接在原切片基础上进行切片,会导致内存得不到释放。
for vs for range
for range 是一个语法糖,range 对每个迭代值都创建了一个拷贝,如果每次迭代的值内存占用很小的情况下,for 和 range 的性能几乎没有差异,但是如果每个迭代值内存占用很大。那性能差距就很明显了。
这个时候建议用指针,指针指向这个迭代的对象。
避免用反射 reflect
反射效率很低,特别是 go 自带的 json 的 Marshal 和 Unmarshal 方法。推荐用高性能的 json 工具。
用空结构体节省内存
Go 语言中空结构体不占据内存,因此被广泛作为各种场景下的占位符使用。节省资源 + 本身具备很强语义。
fmt.Println(unsafe.Sizeof(struct{}{}))
0
空结构体不需要进行内存对齐,所以不占用空间。
注意:struct 作为结构体最后一个字段时,需要内存对齐。因为如果有指针指向该字段, 返回的地址将在结构体之外,如果此指针一直存活不释放对应的内存,就会有内存泄露的问题(该内存不因结构体释放而释放)。